package com.googlecode.perfectable.visuable.parse;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.List;

import org.w3c.dom.CharacterData;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.googlecode.perfectable.eventable.routing.Source;
import com.googlecode.perfectable.prosperable.message.ClientMessage;
import com.googlecode.perfectable.visuable.structure.Attribute;
import com.googlecode.perfectable.visuable.structure.Elements;
import com.googlecode.perfectable.visuable.visual.IdentifiedVisual;
import com.googlecode.perfectable.visuable.visual.IdentifiedVisuals;
import com.googlecode.perfectable.visuable.visual.StandardVisual;
import com.googlecode.perfectable.visuable.visual.Visual;
import com.googlecode.perfectable.visuable.visual.StandardVisual.ElementVisualBuilder;
import com.googlecode.perfectable.visuable.visual.custom.ButtonVisual;
import com.googlecode.perfectable.visuable.visual.custom.HeadVisual;
import com.googlecode.perfectable.visuable.visual.custom.InputVisual;
import com.googlecode.perfectable.visuable.visual.custom.TableVisual;

public class StandardVisualConverter implements VisualConverter {
	private final VisualConverter htmlConverter;
	private final VisualRegistry registry;
	private final StandardConfigurationBuilder configurationBuilder;

	public StandardVisualConverter(VisualRegistry registry, StandardConfigurationBuilder configurationBuilder,
			Source<ClientMessage> clientMessageSource) {
		this.registry = registry;
		this.configurationBuilder = configurationBuilder;
		this.htmlConverter = new DispatchingConverter(this, clientMessageSource);
	}

	@Override
	public Visual convertVisual(Element element) {
		checkNotNull(element);
		Visual result = this.htmlConverter.convertVisual(element);
		if(result instanceof IdentifiedVisual) {
			this.registry.put((IdentifiedVisual) result);
		}
		this.configurationBuilder.buildConfiguration(result, Elements.childNodes(element));
		return result;
	}

	private static final class DispatchingConverter implements VisualConverter {
		private final VisualConverter headConverter;
		private final VisualConverter inputConverter;
		private final VisualConverter buttonConverter;
		private final VisualConverter tableConverter;
		private final VisualConverter boxConverter;

		public DispatchingConverter(VisualConverter parent, Source<ClientMessage> clientMessageSource) {
			this.headConverter = new HeadVisualConverter();
			this.inputConverter = new InputVisualConverter(clientMessageSource);
			this.buttonConverter = new ButtonVisualConverter(clientMessageSource);
			this.tableConverter = new TableVisualConverter();
			this.boxConverter = new BoxVisualConverter(parent);
		}

		@Override
		public Visual convertVisual(Element element) {
			String elementName = checkNotNull(element.getLocalName());
			Visual visual;
			if(HeadVisual.ELEMENT_NAME.equals(elementName)) {
				visual = this.headConverter.convertVisual(element);
			}
			else if(InputVisual.ELEMENT_NAME.equals(elementName)) {
				visual = this.inputConverter.convertVisual(element);
			}
			else if(ButtonVisual.ELEMENT_NAME.equals(elementName)) {
				visual = this.buttonConverter.convertVisual(element);
			}
			else if(TableVisual.ELEMENT_NAME.equals(elementName)) {
				visual = this.tableConverter.convertVisual(element);
			}
			else {
				visual = this.boxConverter.convertVisual(element);
			}
			return visual;
		}
	}

	private static final class HeadVisualConverter implements VisualConverter {
		public HeadVisualConverter() {
			// prevent syntetic access
		}

		@Override
		public Visual convertVisual(Element element) {
			Elements.checkName(element, HeadVisual.ELEMENT_NAME, DocumentConstants.XHTML_NAMESPACE);
			// MARK set title, append "link"s and so on
			return HeadVisual.create(); // MARK inline
		}

	}

	private static final class InputVisualConverter implements VisualConverter {
		private final Source<ClientMessage> clientMessageSource;

		public InputVisualConverter(Source<ClientMessage> clientMessageSource) {
			this.clientMessageSource = clientMessageSource;
		}

		@Override
		public Visual convertVisual(Element element) {
			Elements.checkName(element, InputVisual.ELEMENT_NAME, DocumentConstants.XHTML_NAMESPACE);
			List<Attribute> attributes = Attribute.convert(element.getAttributes());
			InputVisual result = InputVisual.create(attributes);
			result.registerMessageHandlers(this.clientMessageSource);
			return result;
		}
	}

	private static final class ButtonVisualConverter implements VisualConverter {
		private final Source<ClientMessage> clientMessageSource;

		public ButtonVisualConverter(Source<ClientMessage> clientMessageSource) {
			this.clientMessageSource = clientMessageSource;
		}

		@Override
		public Visual convertVisual(Element element) {
			Elements.checkName(element, ButtonVisual.ELEMENT_NAME, DocumentConstants.XHTML_NAMESPACE);
			String text = element.getTextContent(); // MARK is it really never null?
			List<Attribute> attributes = Attribute.convert(element.getAttributes());
			String identifier = IdentifiedVisuals.extractIdentifier(attributes);
			ButtonVisual result = ButtonVisual.create(identifier, text);
			result.registerMessageHandlers(this.clientMessageSource);
			return result;
		}
	}

	private static final class TableVisualConverter implements VisualConverter {
		public TableVisualConverter() {
			// prevent syntetic access
		}

		@Override
		public Visual convertVisual(Element element) {
			Elements.checkName(element, TableVisual.ELEMENT_NAME, DocumentConstants.XHTML_NAMESPACE);
			return TableVisual.create();
		}
	}

	private static final class BoxVisualConverter implements VisualConverter {
		private final VisualConverter parent;

		public BoxVisualConverter(VisualConverter parent) {
			this.parent = parent;
		}

		@Override
		public Visual convertVisual(Element element) {
			// Elements.checkName(element, StandardVisual.ELEMENT_NAME, DocumentConstants.XHTML_NAMESPACE);
			String elementName = checkNotNull(element.getLocalName());
			ElementVisualBuilder builder = StandardVisual.builder(elementName);
			List<Attribute> attributes = Attribute.convert(element.getAttributes());
			builder.withAttributes(attributes);
			for(Node childNode : Elements.childNodes(element)) {
				if(childNode instanceof Element) {
					Element childElement = (Element) childNode;
					if(!DocumentConstants.XHTML_NAMESPACE.equals(childElement.getNamespaceURI())) {
						continue;
					}
					Visual childVisual = this.parent.convertVisual(childElement);
					builder.withChild(childVisual);
				}
				else if(childNode instanceof CharacterData) {
					CharacterData childCharacterData = (CharacterData) childNode;
					String text = childCharacterData.getTextContent();
					builder.withTextPart(text);
				}
				// else pass
			}
			return builder.build();
		}
	}
}
